﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Security;
using System.Web;
using System.Web.Caching;
using System.Configuration;

namespace Hengbei.BLL
{
    /// <summary>
    /// Mapping between application role and AD group.
    /// </summary>
    public class RoleMappingTuple
    {
        public RoleMappingTuple(string applicationRoleName, string adGroupName)
        {
            this.ApplicationRoleName = applicationRoleName;
            this.ADGroupName = adGroupName;
        }

        public string ApplicationRoleName;
        public string ADGroupName;
    }

    /// <summary>
    /// Gets role information from AD group membership and map to custom-defined application roles. 
    /// </summary>
    public class ApplicationMembershipProvider : SqlMembershipProvider
    {
        public override bool ValidateUser(string username, string password)
        {
            bool hasPassedValidation = false;
            hasPassedValidation = UserService.ValidateUser(username, password);
            return hasPassedValidation;
        }

        /// <summary>
        /// Get the internal cache key for windows groups
        /// </summary>
        /// <param name="userName">Logon user name</param>
        /// <returns>Cache key value</returns>
        protected string GetADGroupCacheKey(string userName)
        {
            return userName + ":" + base.ApplicationName + ":adGroup";
        }

        /// <summary>
        /// Get the internal cache key for application roles
        /// </summary>
        /// <param name="userName">Logon user name</param>
        /// <returns>Cache key value</returns>
        protected string GetAppRoleCacheKey(string userName)
        {
            return userName + ":" + base.ApplicationName + ":appRole";
        }

        /// <summary>
        /// Map application roles to AD groups
        /// </summary>
        protected Dictionary<string, List<string>> ApplicationRoleMapping
        {
            get
            {
                if (applicationRoleMapping == null)
                {
                    applicationRoleMapping = new Dictionary<string, List<string>>();
                    adGroupMapping = new Dictionary<string, List<string>>();
                    OnLoadRoleMappings();
                }
                return applicationRoleMapping;
            }
        }
        private Dictionary<string, List<string>> applicationRoleMapping;

        /// <summary>
        /// Map AD groups to application roles
        /// </summary>
        protected Dictionary<string, List<string>> ADGroupMapping
        {
            get
            {
                if (adGroupMapping == null)
                {
                    applicationRoleMapping = new Dictionary<string, List<string>>();
                    adGroupMapping = new Dictionary<string, List<string>>();
                    OnLoadRoleMappings();
                }
                return adGroupMapping;
            }
        }
        private Dictionary<string, List<string>> adGroupMapping;

        /// <summary>
        /// Delegate function to get the mapping relationship between App Roles and AD Groups.
        /// </summary>
        /// <returns>List of Role Mappings.</returns>
        public delegate List<RoleMappingTuple> LoadRoleMappingsDelegate();
        public LoadRoleMappingsDelegate LoadRoleMappings = delegate { throw new NotImplementedException("LoadRoleMappings delegate is not implemented."); };

        /// <summary>
        /// Invoke the LoadRoleMappings delegate to load role mapping information from external source.
        /// </summary>
        protected virtual void OnLoadRoleMappings()
        {
            List<RoleMappingTuple> roleMappings = LoadRoleMappings();
            applicationRoleMapping.Clear();
            adGroupMapping.Clear();
            if (roleMappings != null)
            {
                foreach (RoleMappingTuple pair in roleMappings)
                {
                    pair.ADGroupName = pair.ADGroupName.ToUpper();
                    pair.ApplicationRoleName = pair.ApplicationRoleName.ToUpper();
                    if (!applicationRoleMapping.ContainsKey(pair.ApplicationRoleName))
                    {
                        applicationRoleMapping.Add(pair.ApplicationRoleName, new List<string>());
                    }
                    applicationRoleMapping[pair.ApplicationRoleName].Add(pair.ADGroupName);
                }
                foreach (RoleMappingTuple pair in roleMappings)
                {
                    pair.ADGroupName = pair.ADGroupName.ToUpper();
                    pair.ApplicationRoleName = pair.ApplicationRoleName.ToUpper();
                    if (!adGroupMapping.ContainsKey(pair.ADGroupName))
                    {
                        adGroupMapping.Add(pair.ADGroupName, new List<string>());
                    }
                    adGroupMapping[pair.ADGroupName].Add(pair.ApplicationRoleName);
                }
            }
        }

        /// <summary>
        /// Reload the AD group and Application Role mappings from external source.
        /// </summary>
        /// <param name="username">User name</param>
        public void ReloadUserRoleMappings(string username)
        {
            HttpContext.Current.Cache.Remove(GetADGroupCacheKey(username));
            HttpContext.Current.Cache.Remove(GetAppRoleCacheKey(username));
            OnLoadRoleMappings();
        }

        /// <summary>
        /// Gets a value indicating whether the given user is in the specified AD group or application role.
        /// </summary>
        /// <param name="username">User name</param>
        /// <param name="roleName">Application role name or AD group</param>
        /// <returns>True if the user is in the specific role</returns>
        //public override bool IsUserInRole(string username, string roleName)
        //{
        //    //check base role
        //    if (base.IsUserInRole(username, roleName))
        //    {
        //        return true;
        //    }
        //    else //check application roles
        //    {
        //        string[] mappedRoles = MapAppRolesToADGroups(new string[] { roleName });
        //        foreach (string mappedRole in mappedRoles)
        //        {
        //            if (base.IsUserInRole(username, mappedRole))
        //                return true;
        //        }
        //        return false;
        //    }
        //}

        /// <summary>
        /// Gets a list of application roles that are mapped to the AD groups by looking up the mapping dictionary.
        /// </summary>
        /// <param name="appRoles">Application role names (case-insensitive)</param>
        /// <returns>AD groups that are mapped to the give application role</returns>
        private string[] MapAppRolesToADGroups(string[] appRoles)
        {
            List<string> roles = new List<string>();
            foreach (string appRole in appRoles)
            {
                List<string> mappedRoles;
                if (ApplicationRoleMapping.TryGetValue(appRole.ToUpper(), out mappedRoles))
                {
                    //Only add distinct roles to the list.
                    foreach (string newRole in mappedRoles)
                    {
                        if (!roles.Contains(newRole))
                            roles.Add(newRole);
                    }
                }
            }
            return roles.ToArray();
        }

        /// <summary>
        /// Gets a list of AD groups that are mapped to application roles by looking up the mapping dictionary.
        /// </summary>
        /// <param name="adGroups">Active Directory group names (case-insensitive)</param>
        /// <returns>Application roles that are mapped to the give AD groups</returns>
        private string[] MapADGroupsToAppRoles(string[] adGroups)
        {
            List<string> roles = new List<string>();
            foreach (string adGroup in adGroups)
            {
                List<string> mappedRoles;
                if (ADGroupMapping.TryGetValue(adGroup.ToUpper(), out mappedRoles))
                {
                    //Only add distinct roles to the list.
                    foreach (string newRole in mappedRoles)
                    {
                        if (!roles.Contains(newRole))
                            roles.Add(newRole);
                    }
                }
            }
            return roles.ToArray();
        }
    }
}
